//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
//     Copyright (c) Telligent Systems Corporation.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;

namespace CommunityServer.Components {

	public class Formatter {

        /// <summary>
        /// Many WYSWIGY editors accidently convert local Urls to relative Urls. ConvertLocalUrls looks for these
        /// and attempts to convert them back.
        /// </summary>
        public static string ConvertLocalUrls(string body, string host)
        {
			if (!host.EndsWith("/"))
				host += "/";

			body = Regex.Replace(body, "href=\"/","href=\"" + host, RegexOptions.IgnoreCase);
			body = Regex.Replace(body, "src=\"/","src=\"" + host, RegexOptions.IgnoreCase);

            return body;
        }


        /// <summary>
        /// We do not allow any markup in comments. This method will transform links to full hrefs and 
        /// add the ref=nofollow attribute
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static string SafeFeedback(string text)
        {
            if(Globals.IsNullorEmpty(text))
                return text;

            text = Globals.HtmlEncode(text);

            //Find any links
            string pattern = @"(http|ftp|https):\/\/[\w]+(.[\w]+)([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])";
            MatchCollection matchs;
			StringCollection uniqueMatches = new StringCollection();
            
            matchs = Regex.Matches(text,pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
            foreach (Match m in matchs) 
            {
				if (!uniqueMatches.Contains(m.ToString()))
				{
					text = text.Replace(m.ToString(), "<a rel=\"nofollow\" target=\"_new\" href=\"" + m.ToString() + "\">" + m.ToString() + "</a>");
					uniqueMatches.Add(m.ToString());
				}
            }

			// Replace Line breaks with <br> and every other concurrent space with &nbsp; (to allow line breaking)
			text = ConvertTextToParagraph(text);// text.Replace("\n", "<br />");
			text = text.Replace("  ", " &nbsp;");
			
            return text;
        }

        public static string ConvertTextToParagraph(string text)
        {
            if(Globals.IsNullorEmpty(text))
                return text;

            text = Regex.Replace(text,"<p>","",RegexOptions.IgnoreCase); 
            text = Regex.Replace(text,"</p>","\n",RegexOptions.IgnoreCase); 

            text = text.Replace ("\r\n", "\n").Replace ("\r", "\n");
            text += "\n\n";
		
            text = text.Replace("\n\n","\n");		

            string[] lines = text.Split('\n');
				
            StringBuilder paragraphs = new StringBuilder();
		
            foreach(string line in lines)
            {			
                if(line != null && line.Trim().Length > 0)
                    paragraphs.AppendFormat("<p>{0}</p>\n",line);
            }		
		
            return paragraphs.ToString();
        }

		#region FormatFontSize
		public static string FormatFontSize(double fontsize) {

			CSContext csContext = CSContext.Current;

			if (!csContext.User.IsAnonymous) {
				int size = csContext.User.Profile.FontSize;

				switch (size) {
					case -1:
						fontsize = fontsize - (fontsize * .02);
						break;

					case 1:
						fontsize = fontsize + (fontsize * .15);
						break;

					case 2:
						fontsize = fontsize + (fontsize * .30);
						break;

				}
			}

			return fontsize.ToString().Replace(",",".") + "em";
		}
		#endregion

		#region Format Date
		public static string FormatDate (DateTime date) {
			return FormatDate(date, false);
		}

		public static string FormatDate (DateTime date, bool showTime) {
		    CSContext csContext = CSContext.Current;
			string dateFormat = csContext.User.Profile.DateFormat;
            
			if (csContext.User.UserID > 0) {
				dateFormat = csContext.User.Profile.DateFormat;
				date = csContext.User.GetTimezone(date);
			}

			if (showTime)
				return date.ToString(dateFormat + " " + csContext.SiteSettings.TimeFormat);
			else
				return date.ToString(dateFormat);
		}
		#endregion


		#region FormatAgoDate
		public static string FormatAgoDate (DateTime date) 
		{
			CSContext context = CSContext.Current;
			DateTime serverDateTime = UserTime.CurrentServerTime;


			if (date > serverDateTime.AddHours(-24) ) 
			{
				int minutes = (serverDateTime - date).Minutes;
				int hours = (serverDateTime - date).Hours;

				if (date > serverDateTime.AddHours(-1) ) 
				{
					if (minutes < 2)
						return ResourceManager.GetString("date_minuteago");

					return string.Format(ResourceManager.GetString("date_minutesago"), minutes);
				} 
				else 
				{
					if (hours < 2)
						return string.Format(ResourceManager.GetString("date_hourago"), hours, minutes);

					return string.Format(ResourceManager.GetString("date_hoursago"), hours, minutes);
				}

			} 
			else 
			{
				string dateFormat;
				User user;

				// Optimizing code to only grab GetUser() once, since it is a lot of overhead.
				//
				user = context.User;

				// Setting up the user's date profile
				//
				if (user.UserID > 0) 
				{
					date = user.GetTimezone(date);
					dateFormat = user.Profile.DateFormat;
				} 
				else 
				{
					// date is already set
					dateFormat = context.SiteSettings.DateFormat;
				}
				return date.ToString(dateFormat) + ", " + date.ToString(context.SiteSettings.TimeFormat);
			}
		}		
		#endregion


		#region Format Date Post
		// Used to return a date in Word format (Today, Yesterday, Last Week, etc)
		//
		public static string FormatDatePost (DateTime date) 
		{
			// This doesn't have to be as complicated as the FormatLastPost.
			// TODO: Need to optimize FormatLastPost (the multiple calls to GetUser()).
			//
			string returnItem;
			string dateFormat;
			DateTime userLocalTime;
			CSContext cntx = CSContext.Current;
			User user = cntx.User;
			
			// Setting up the user's date profile
			//
			if (user.UserID > 0) {
				date = user.GetTimezone(date);
				dateFormat = user.Profile.DateFormat;
				userLocalTime = user.GetTimezone(DateTime.Now);
			} else {
				// date is already set
				dateFormat = cntx.SiteSettings.DateFormat;
				userLocalTime = DateTime.Now;
			}
			
			// little error checking
			//
			if (date < DateTime.Now.AddYears(-20) )
				return ResourceManager.GetString("NumberWhenZero");
			
			// make Today and Yesterday bold for now...
			//
			if ((date.DayOfYear == userLocalTime.DayOfYear) && (date.Year == userLocalTime.Year)) {

				returnItem = ResourceManager.GetString("TodayAt");
				returnItem +=  date.ToString(cntx.SiteSettings.TimeFormat);

			} else if ((date.DayOfYear == (userLocalTime.DayOfYear - 1)) && (date.Year == userLocalTime.Year)) {

				returnItem = ResourceManager.GetString("YesterdayAt");
				returnItem += date.ToString(cntx.SiteSettings.TimeFormat);

			} else {

				returnItem = date.ToString(dateFormat) + ", " + date.ToString(cntx.SiteSettings.TimeFormat);

			}
			return returnItem;
		}
		#endregion

		#region Expand/Collapse Icon
		public static string ExplandCollapseIcon (Group group) {

			if (group.HideSections)
				return Globals.GetSkinPath() +"/images/expand-closed.gif";
            
			return Globals.GetSkinPath() +"/images/expand-open.gif";
            
		}
		#endregion

        #region Format number
        public static string FormatNumber (int number) {
            return FormatNumber (number, ResourceManager.GetString("NumberFormat"), ResourceManager.GetString("NumberWhenZero"));
        }

        public static string FormatNumber (int number, string whenZero) {
            return FormatNumber (number, ResourceManager.GetString("NumberFormat"), whenZero);
        }

        public static string FormatNumber (int number, string format, string whenZero) {

            if (number == 0)
                return whenZero;
            else
                return number.ToString(format);

        }
        #endregion

        #region String length formatter
		public static string Truncate(string text, int maxLength)
		{
			return Truncate(text, maxLength, false);
		}

		public static string Truncate(string text, int maxLength, bool includeTruncationIndicator)
		{
			if (text.Length <= maxLength)
				return text;

			if (includeTruncationIndicator)
                return text.Substring(0, maxLength - 1) + "...";

			return text.Substring(0, maxLength - 1);
		}

        public static string CheckStringLength (string stringToCheck, int maxLength) 
        {
            // TODO: We should put simple string shrinking inline,
            // without a subsequent call to the comprehensive method,
            // for performance reasons.
            //
            return CheckStringLength( stringToCheck, maxLength, false );
        }

        public static string CheckStringLength (string stringToCheck, int maxLength, bool addCharsUntillSpace) 
        {
            string checkedString = null;

            // Stop here if we have a null, an empty or a shorter string
            //
            if (stringToCheck == null)
            {
                return string.Empty; // or null?!
            }
            else if (stringToCheck.Length == 0 || 
                stringToCheck.Length <= maxLength)
            {
                return stringToCheck;
            }

            // Begin stripping off extra html chars
            //
            stringToCheck = Globals.HtmlDecode( stringToCheck );
            
            // Check again if we should stop here
            //
            if (stringToCheck.Length <= maxLength)
                return Globals.HtmlEncode( stringToCheck );

            // The string to check is longer than maxLength
            // and we need to shrink it.
            //
            checkedString = stringToCheck.Substring( 0, maxLength ).Trim();
            
            // Include the remaining word if we have more words till the end
            //
            if (addCharsUntillSpace &&
                stringToCheck.Substring( checkedString.Length, (stringToCheck.Length - checkedString.Length) ).IndexOf( " " ) != -1)
            {
                char c;

                // We may be in a middle of a word while shrinking the string
                //
                int idx = checkedString.Length;

                // Go till the end of the word
                //
                while ((idx < stringToCheck.Length) && 
                        (c = stringToCheck[idx]) != ' ')
                {
                    checkedString += c;
                    idx++;    
                }
            }
            
            // Html encode back
            //
            checkedString = Globals.HtmlEncode( checkedString + "..." );

            return checkedString;
        }
        #endregion

        #region Search Formatting

        public static string GetBodySummary (string text, int size, string terms, Color color, Color bgColor) 
        {
            string[] highlightWords = terms.Split(' ');

            return GetBodySummary (text, size, highlightWords, color, bgColor);
        }

        public static string GetBodySummary(string text, int size, string[] highlightWords, Color color, Color bgColor) 
        {
            // EAD 6/27/04: New function I wrote a month ago to strip all
            // tags out and replace with line breaks.  In this case, we need to
            // insert <br />s inplace of line breaks so it looks somewhat neat.
            //
            // LN 6/23/04 : Do not Html encode the body
            // postBody = Globals.HtmlEncode(Transforms.StripHtmlXmlTags(postBody));
            // postBody = Transforms.StripHtmlXmlTags(postBody);
            string postBody = Formatter.StripAllTags(text);

            // We only want to display some of the body
            //
            if (size > 0 && postBody.Length > size) 
            {
                int whitespace = 0;
                // Clip the body
                postBody = postBody.Substring(0, size);

                // Find the last occurence of a space
                whitespace = postBody.LastIndexOf(" ");

                if( whitespace == -1 )
                    whitespace = size;
				
                // Rebuild postBody string
                postBody = postBody.Substring(0, whitespace) + " ...";
            }

            // Do any word highlighting
            //
            if (highlightWords.Length > 0 ) 
            {
                string delimitedString;

                // Split and delimit string
                //
                delimitedString = Transforms.ToDelimitedString(highlightWords, "|");

                // TDD 7/19/2004
                // this string is used as the regex pattern. if it contains special characters like (,* $ it
                // throw an error since these are special instruction characters to Regex. We must escape these
                // special characters if they are used as search strings.
                delimitedString = Regex.Escape( delimitedString );
                // Perform replacement
                //
                postBody = Regex.Replace(postBody, delimitedString, "<span style=\"color:" + color.Name + ";background-color:" + bgColor.Name + "\"><b>$0</b></span>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);
            }

            return postBody;
        }

        #endregion

		#region Strip All Tags from a String
		/*
		 * Takes a string and strips all bbcode and html from the
		 * the string. Replacing any <br />s with linebreaks.  This
		 * method is meant to be used by ToolTips to present a
		 * a stripped-down version of the post.Body
		 *
		 */
		public static string StripAllTags ( string stringToStrip ) 
        {
			// paring using RegEx
			//
			stringToStrip = Regex.Replace(stringToStrip, "</p(?:\\s*)>(?:\\s*)<p(?:\\s*)>", "\n\n", RegexOptions.IgnoreCase | RegexOptions.Compiled);
			stringToStrip = Regex.Replace(stringToStrip, "<br(?:\\s*)/>", "\n", RegexOptions.IgnoreCase | RegexOptions.Compiled);
			stringToStrip = Regex.Replace(stringToStrip, "\"", "''", RegexOptions.IgnoreCase | RegexOptions.Compiled);
			stringToStrip = Transforms.StripHtmlXmlTags( stringToStrip );

			return stringToStrip;
		}

        /// <summary>
        /// Limits the lenght of a string and accounts for white spaces
        /// </summary>
        public static string MaxLength(string text, int charLimit)
        {
            if(Globals.IsNullorEmpty(text))
                return string.Empty;

            if(charLimit >= text.Length)
                return text;

            int place = charLimit;
            int len = text.Length - 1;

            while(!Char.IsWhiteSpace(text[place]) && place < len)
            {
                place ++;
            }

            return text.Substring(0,place);
        }

        public static string RemoveHtml(string html)
        {
           return RemoveHtml(html,0);
        }

        #region reusable regex's
        protected static Regex htmlRegex = new Regex("<[^>]+>|\\&nbsp\\;", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        protected static Regex spacer = new Regex(@"\s{2,}", RegexOptions.Compiled);
        #endregion

        public static string RemoveHtml(string html, int charLimit)
        {
            if(html == null || html.Trim().Length == 0)
                return string.Empty;
           
            string nonhtml = spacer.Replace(htmlRegex.Replace(html, " ").Trim()," ");

            if(charLimit <= 0 || charLimit >= nonhtml.Length)
                return nonhtml;

            int place = charLimit;
            int len = nonhtml.Length - 1;

            while(!Char.IsWhiteSpace(nonhtml[place]) && place < len)
            {
                place ++;
            }

            return nonhtml.Substring(0,place);


        }

		#endregion

		#region Format post emoticon
		public static string GetEmotionMarkup (int emoticonID) {

            const string imgFormat = "<img src=\"{0}{1}\" alt=\"{2}\" />";
            string forumHomePath = Globals.GetSiteUrls().Emoticon;

            // If we aren't using Emoticons, return an empty string.
            if ( (emoticonID == 0) || (!CSContext.Current.SiteSettings.EnableEmoticons) )
				return "";

            ArrayList emoticonTxTable = Smilies.GetSmilies();

            // EAD 2-9-2005: Removed try/catch block as this will return null if doesn't exist and changed
            // logic to detect the null returned.
            Smiley smiley = new Smiley();
            smiley = Smilies.GetSmiley( emoticonID );

            if (smiley != null)
                return string.Format(imgFormat, forumHomePath, smiley.SmileyUrl, smiley.SmileyText + " [" + smiley.SmileyCode + "]");
            else
                return "";

		}
		#endregion

        #region Format edit notes
        public static string EditNotes (string notes) {

            if (notes == null)
                return String.Empty;

            //string editNotesTable = "<table width=\"75%\" class=\"editTable\"><tr><td>{0}</td></tr></table>";
            //return string.Format(editNotesTable, Transforms.FormatPost(notes, PostType.HTML)) + Globals.HtmlNewLine + Globals.HtmlNewLine; 
			return Transforms.FormatPostText(notes, PostContentType.HTML);

        }
        #endregion

		#region Format Post Points
		public static string PostPoints (Post post)
		{
			CSContext csContext = CSContext.Current;

			if (!csContext.SiteSettings.EnablePointSystem)
				return string.Empty;

			bool canSee = false;
			switch (csContext.SiteSettings.PointsPostDisplayLevel)
			{
				case DisplayLevel.Everyone:
					canSee = true;
					break;
				case DisplayLevel.AdministratorsAndModerators:
					if (csContext.User.IsAdministrator || csContext.User.IsModerator)
						canSee = true;
					break;
			}

			if (canSee)
				return string.Format(ResourceManager.GetString("PostFlatView_PostPoints"), post.Points);
			else
				return string.Empty;
				
		}
		#endregion

        #region Format Post IP Address
        public static string PostIPAddress (Post post) 
		{
        	CSContext cntx = CSContext.Current;
			SiteSettings settings = cntx.SiteSettings;
        	if (post.UserHostAddress == "000.000.000.000")
                return string.Format(ResourceManager.GetString("PostFlatView_IPAddress"), ResourceManager.GetString("NotLogged"));

            if (settings.DisplayPostIP) {
                return string.Format(ResourceManager.GetString("PostFlatView_IPAddress"), post.UserHostAddress);
            } else if ((settings.DisplayPostIPAdminsModeratorsOnly) && ((cntx.User.IsForumAdministrator) || (cntx.User.IsModerator)) ){ 
                return string.Format(ResourceManager.GetString("PostFlatView_IPAddress"), post.UserHostAddress);
            } else {
                return string.Format(ResourceManager.GetString("PostFlatView_IPAddress"), ResourceManager.GetString("Logged"));
            }

        }
        #endregion

        #region Format Whitespace
        public static string Whitespace(int height, int width, bool preBreak, bool postBreak) {
            string imgTag = string.Format("<img width=\"{1}\" height=\"{0}\" src=\"" + Globals.ApplicationPath + "/Utility/images/1x1.gif\" />", height, width);

            if (preBreak)
                imgTag = "<br />" + imgTag;

            if (postBreak)
                imgTag = imgTag + "<br />";

            return imgTag;

        }
        
        #endregion

        #region Format Username
        public static string FormatUsername (int userID, string username) {
            return FormatUsername( userID, username, false, true );
        }
        
        public static string FormatUsername (int userID, string username, bool showAsAnonymous) {
            return FormatUsername( userID, username, showAsAnonymous, false );
        }
        
        public static string FormatUsername (int userID, string username, bool showAsAnonymous, bool useSpanTag) {
           return FormatUsername( userID, username, showAsAnonymous, useSpanTag, false);
        }

		public static string FormatUsername(int userID, string username, bool showAsAnonymous, bool useSpanTag, bool isIgnoredPost)
		{
			if (!isIgnoredPost)
			{
				// Added actual param showAsAnonymous to display a normal user as anonymous
				// if it is required so.
				//
				if((username == "") || (userID == 0))
					username = ResourceManager.GetString("DefaultAnonymousUsername");

				User user = Users.GetUser(userID, false);
				string displayName = user.DisplayName;

				if(user.IsAnonymous)
				{
					if(user.Username != username)
						displayName = username;
					else if(showAsAnonymous)
						displayName = ResourceManager.GetString("DefaultAnonymousUsername");
				}

				if (useSpanTag)
					return "<span class=\"inlineLink\" onclick=\"window.open('" + Globals.GetSiteUrls().UserProfile(user.Username) + "')\">" + displayName + "</span>";
				else
					return "<a href=\"" + Globals.GetSiteUrls().UserProfile(user.Username) + "\">" + displayName + "</a>";
			}
			else 
			{
				return ResourceManager.GetString("IgnoredPost_Username");;
			}
		}
        #endregion
		
		public static string FormatIrcCommands (string postText, string postFrom) {
			return FormatIrcCommands (postText, postFrom, "");
		}

		public static string FormatIrcCommands (string postText, string postFrom, string postTo) {
			// This is for our pure enjoyment. - Terry & Eric
			//

			// /me slaps Terry with a big-mouth trout.
			//
			postText = Regex.Replace(postText, @"(>/me\b|\n>/me)(.*?|\n)(<|\n|<\n)", "><span class=\"txtIrcMe\">&nbsp;*&nbsp;" + postFrom + "$2</span><", RegexOptions.IgnoreCase | RegexOptions.Compiled);
			postText = Regex.Replace(postText, @"(\n/me\b)(.*?|\n)(\n)", "<span class=\"txtIrcMe\">&nbsp;*&nbsp;" + postFrom + "$2</span>", RegexOptions.IgnoreCase | RegexOptions.Compiled);

			// /you
			//
			// TODO: when we figure out how to grab the "Replying To" from
			// create/edit post.


			return postText;
		}

        public static string FormatUserRank (Int32 postCount, bool showAsPicture) {
            ArrayList ranks = Ranks.GetRanks();
            return FormatUserRank( postCount, showAsPicture, ranks );
        }

        public static string FormatUserRank (Int32 postCount, bool showAsPicture, ArrayList ranks) { 
            string rankName = "";
            string rankUrlPicture = "";
            string imgHtmlTag = "<img src=\"{0}\" border=\"0\" alt=\"{1}\">";
            
            if (ranks == null)
                return ResourceManager.GetString( "Utility_UserRank_NotAvailable" );

            foreach (Rank rank in ranks) { 
                if (postCount >= rank.PostingCountMinimum && 
                    postCount <= rank.PostingCountMaximum) { 

                    rankName = rank.RankName.Trim();
                    rankUrlPicture = rank.RankIconUrl.Trim();
                    break;
                }
            }
            
            // Exit if there is an empty rank name
            //
            if (rankName.Length == 0) { 
                return ResourceManager.GetString( "Utility_UserRank_NotAvailable" );
            }
            
            // Do we need to display a pictured rank?
            //
            if (showAsPicture && rankUrlPicture.Length > 0) {
                //string pictureUrl = Globals.GetSkinPath() + "/images/" + rankUrlPicture;

                // TODO: Check for picture availability ?!
                //

                return string.Format( imgHtmlTag, rankUrlPicture, rankName );
            } 
            
            return rankName;
        }

        #region Format Moderator Actions
        /// <summary>
        /// Gets a localized description for provided ModeratorAction.
        /// </summary>
        public static string FormatModeratorAction (ModeratorActions action) {
            return ResourceManager.GetString( "Utility_ModeratorActions_" + action.ToString() );
        }
        #endregion

        #region Format User Audit Counters
        /// <summary>
        /// Formats user moderation counters following this pattern "[message_actions] / [forum_actions]".
        /// If one of the ingredients are 0 then we will display a '-' character instead. 
        /// </summary>
        public static string FormatUserAuditCounters (int userID, AuditSummary summary) {
            string outString = "{0} / {1}";
            string counterLink = "<a href=\"{0}\">{1}</a>";
            int[] counters = Audit.GetUserAuditCounters( summary );

            if (counters == null) { 
                return string.Format( outString, "-", "-" );
            }

            
            if (counters[0] > 0 && counters[2] > 0)
                return string.Format( outString, string.Format( counterLink, SiteUrls.Instance().UserModerationHistory( userID, counters[1] ), counters[0] ), string.Format( counterLink, SiteUrls.Instance().UserModerationHistory( userID, counters[3] ), counters[2] ) );
            else {
                if (counters[0] == 0 && counters[2] > 0)
                    return string.Format( outString, "-", string.Format( counterLink, SiteUrls.Instance().UserModerationHistory( userID, counters[3] ), counters[2] ) );
                else 
                    if (counters[0] > 0 && counters[2] == 0)
                        return string.Format( outString, string.Format( counterLink, SiteUrls.Instance().UserModerationHistory( userID, counters[1] ), counters[0] ), "-" );
                    else
                        return string.Format( outString, "-", "-" );
            }
        }
        #endregion

        #region Format ban reason
        public static string FormatBanReason (UserBanReason banReason) { 
            return ResourceManager.GetString( "Utility_UserBanReason_" + banReason.ToString() );
        }
        #endregion
    }

}
